package com.google.code.fluentjoiner.jdbc;

import com.google.code.fluentjoiner.IteratorSupplierBuilder;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Iterator;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JDBCIteratorSupplierBuilder
{

    private final static Logger LOGGER = LoggerFactory.getLogger(JDBCIteratorSupplierBuilder.class);

    private static class QueryConfiguration
    {

        public DataSource dataSource = null;
        public String query = null;
        public Object[] parameters = null;

        public QueryConfiguration()
        {
        }

        public QueryConfiguration(final QueryConfiguration queryConfiguration)
        {
            this();
            this.dataSource = queryConfiguration.dataSource;
            this.query = queryConfiguration.query;
            this.parameters = queryConfiguration.parameters;
        }
    }
    private final QueryConfiguration queryConfiguration;

    private JDBCIteratorSupplierBuilder(final QueryConfiguration queryConfiguration)
    {
        this.queryConfiguration = queryConfiguration;
    }

    public static JDBCIteratorSupplierBuilder withDataSource(final DataSource dataSource)
    {
        Preconditions.checkArgument(dataSource != null, "Parameter dataSource cannot be null.");
        final QueryConfiguration newConfiguration = new QueryConfiguration();
        newConfiguration.dataSource = dataSource;
        return new JDBCIteratorSupplierBuilder(newConfiguration);
    }

    public JDBCIteratorSupplierBuilder withQuery(final String query)
    {
        final QueryConfiguration newConfiguration = new QueryConfiguration(queryConfiguration);
        newConfiguration.query = query;
        return new JDBCIteratorSupplierBuilder(newConfiguration);
    }

    public JDBCIteratorSupplierBuilder withParameters(final Object... parameters)
    {
        final QueryConfiguration newConfiguration = new QueryConfiguration(queryConfiguration);
        newConfiguration.parameters = parameters;
        return new JDBCIteratorSupplierBuilder(newConfiguration);
    }

    private static void parseResult(final ResultSet resultSet, final int columnCount, final ImmutableMap<Integer, String> columnNames, final Builder<ImmutableMap<String, Object>> resultBuilder) throws SQLException
    {
        while (resultSet.next())
        {
            final ImmutableMap.Builder<String, Object> rowResultBuilder = ImmutableMap.builder();
            for (int count = 1; count <= columnCount; count++)
            {
                final Object field = resultSet.getObject(count);
                rowResultBuilder.put(columnNames.get(count).toLowerCase(), field);
            }
            resultBuilder.add(rowResultBuilder.build());
        }
    }

    public IteratorSupplierBuilder<ImmutableMap<String, Object>> execute()
    {
        return new IteratorSupplierBuilder<ImmutableMap<String, Object>>()
        {
            @Override
            protected Iterator<ImmutableMap<String, Object>> getSupplierResult()
            {
                Preconditions.checkArgument(queryConfiguration.dataSource != null, "DataSource has not been set.");
                Preconditions.checkArgument(queryConfiguration.query != null, "Query has not been set.");
                final ImmutableList.Builder<ImmutableMap<String, Object>> resultBuilder = ImmutableList.builder();
                Connection connection = null;
                PreparedStatement preparedStatement = null;
                ResultSet resultSet = null;
                try
                {
                    connection = queryConfiguration.dataSource.getConnection();
                    preparedStatement = connection.prepareCall(queryConfiguration.query);
                    resultSet = preparedStatement.executeQuery();
                    final ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
                    final int columnCount = resultSetMetaData.getColumnCount();
                    final ImmutableMap<Integer, String> columnNames = JDBCUtils.getColumnNames(resultSetMetaData);
                    parseResult(resultSet, columnCount, columnNames, resultBuilder);
                }
                catch (final SQLException sqlException)
                {
                    throw new RuntimeException("SQL Exception thrown.", sqlException);
                }
                finally
                {
                    JDBCUtils.closeQuietly(resultSet);
                    JDBCUtils.closeQuietly(preparedStatement);
                    JDBCUtils.closeQuietly(connection);
                }
                return resultBuilder.build().iterator();
            }
        };
    }
}
